# -*- coding: utf-8 -*- #
"""
Time                2023/3/17 20:27
Author:             mingfeng (SunnyQjm)
Email               mfeng@linux.alibaba.com
File                redis_service_registry.py
Description:
"""
import functools
from clogger import logger
from cmg_base import ServiceRegistry, ServiceInstance, CmgUrl, CmgException, \
    ServiceCheck, HealthState
from .common import ClientBase, StaticConst
from .utils import RedisLocker


class RedisServiceRegistry(ServiceRegistry, ClientBase):
    """A redis-based execution module implement of ServiceRegistry
    """

    def __init__(self, url: CmgUrl):
        ServiceRegistry.__init__(self)
        ClientBase.__init__(self, url)

        # Handles Redis implementation of event-centric specialization
        # parameters
        self._self_health_check = self.get_special_param(
            StaticConst.REDIS_SPECIAL_PARM_CMG_ENABLE_SELF_HEALTH_CHECK, True
        )

        # 1. Connect to the Redis server
        self._current_state = HealthState.OFFLINE

    def register(self, service_instance: ServiceInstance):
        """Register one service to redis-based register center"""

        def _on_health_check(service_id: str, state: HealthState):
            if state == HealthState.ONLINE:
                pl.expire(
                    service_id,
                    service_instance.check.get("deregister", 20) * 1.2
                )

        def _do_unregister(service_id: str):
            try:
                self.unregister(service_id)
            except Exception as exc:
                print(exc)

        inner_service_name = StaticConst.get_inner_service_name(
            service_instance.service_name
        )
        inner_service_id = StaticConst.get_inner_service_id(
            service_instance.service_id
        )
        with RedisLocker(self.redis_client,
                         StaticConst.CMG_REDIS_SERVICES_LOCKER) as res:
            if not res:
                raise CmgException("Get locker failed")
            # 1. Record service info to redis
            pl = self.redis_client.pipeline()
            pl.hset(
                inner_service_id, mapping=service_instance.to_redis_mapping()
            )
            pl.sadd(inner_service_name, inner_service_id)
            pl.sadd(StaticConst.CMG_REDIS_SERVICES, inner_service_name)
            pl.expire(
                inner_service_id,
                int(service_instance.check.get("deregister", 20) * 1.2)
            )
            try:
                pl.execute()
            except Exception as exc:
                logger.exception(exc)

            # 2. Deal health check
            if self._self_health_check:
                ServiceCheck.create_health_check_instance(
                    service_instance.check,
                    on_check=functools.partial(_on_health_check,
                                               inner_service_id),
                    do_unregister=functools.partial(
                        _do_unregister, service_instance.service_id
                    )
                ).start()

    def unregister(self, service_id: str):
        """Unregister one service from redis-based register center"""
        # 1. Get service instance
        inner_service_id = StaticConst.get_inner_service_id(
            service_id
        )
        values = self.redis_client.hgetall(inner_service_id)
        if not values:
            raise CmgException(f"Service(service_id={service_id}) not exists")
        # 2. Get service_name from service instance
        service_instance = ServiceInstance.from_redis_mapping(values)
        inner_service_name = StaticConst.get_inner_service_name(
            service_instance.service_name
        )
        with RedisLocker(self.redis_client,
                         StaticConst.CMG_REDIS_SERVICES_LOCKER) as res:
            if not res:
                raise CmgException("Get locker failed")
            # 3. Delete service instance
            pl = self.redis_client.pipeline()
            pl.delete(inner_service_id)
            pl.srem(inner_service_name, inner_service_id)
            pl.scard(inner_service_name)
            results = pl.execute()

            # 4. Delete the service if the service does not contain any
            #    instances
            if results[2] <= 0:
                self.redis_client.srem(StaticConst.CMG_REDIS_SERVICES,
                                       inner_service_name)
